 
package com.example.musicapi.common.util;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
 
/**
 * 参照网址:https://blog.csdn.net/qq_52780611/article/details/128942125
 * @author 
 */
public class RecommendUtil {
    /**
     * 计算皮尔逊积矩相关系数（Pearson product-moment correlation coefficient)
     * 协同
     * @param otherRecommendUsers   其他用户数据
     * @param currentRecommendUser  当前用户数据
     * @return  返回值为当前用户与其他计算的皮尔森相关系数结果集
     */
    private Map<Double, String> computeNearestNeighbor(
            List<RecommendUser> otherRecommendUsers, RecommendUser currentRecommendUser) {
        //计算皮尔逊积矩相关系数（Pearson product-moment correlation coefficient）
        Map<Double, String> distances = new TreeMap<>();
        Map<String, Integer> map = new HashMap<>();
        for (RecommendItem recommendItem1 : currentRecommendUser.getRecommendItemList()) {
            map.put(recommendItem1.getId(), recommendItem1.getScore());
        }
        for (RecommendUser otherRecommendUser : otherRecommendUsers) {
            List<RecommendItem> rating1 = new ArrayList<>();
            List<RecommendItem> rating2 = new ArrayList<>();
            //查找相同的部分
            for (RecommendItem recommendItem2 : otherRecommendUser.getRecommendItemList()) {
                if (map.containsKey(recommendItem2.getId())){
                    rating1.add(new RecommendItem(recommendItem2.getId(),map.get(recommendItem2.getId())));
                    rating2.add(recommendItem2);
                }
            }
            if (rating1.size() == 0){
                continue;
            }
            double distance = pearsonDis(rating1, rating2);
            distances.put(distance, otherRecommendUser.getId());
 
        }
        return distances;
    }
 
 
    /**
     * 计算2个打分序列间的皮尔逊积矩相关系数（Pearson product-moment correlation coefficient）
     * 选择公式四进行计算（百度可查）
     * @param rating1   被计算的用户1（当前用户）
     * @param rating2   被计算的用户2（其他用户之一）
     * @return  返回对应的皮尔森相关系数结果
     */
    private double pearsonDis(List<RecommendItem> rating1, List<RecommendItem> rating2) {
        int n = rating1.size();
        List<Integer> rating1ScoreCollect = rating1.stream().map(t -> t.getScore()).collect(Collectors.toList());
        List<Integer> rating2ScoreCollect = rating2.stream().map(t -> t.getScore()).collect(Collectors.toList());
 
        double Ex= rating1ScoreCollect.stream().mapToDouble(x->x).sum();
        double Ey= rating2ScoreCollect.stream().mapToDouble(y->y).sum();
 
        double Ex2=rating1ScoreCollect.stream().mapToDouble(x->Math.pow(x,2)).sum();
        double Ey2=rating2ScoreCollect.stream().mapToDouble(y->Math.pow(y,2)).sum();
 
        double Exy= IntStream.range(0,n).mapToDouble(i->rating1ScoreCollect.get(i)*rating2ScoreCollect.get(i)).sum();
 
        double numerator=Exy-Ex*Ey/n;
        double denominator=Math.sqrt((Ex2-Math.pow(Ex,2)/n)*(Ey2-Math.pow(Ey,2)/n));
        if (denominator==0){ return 0.0;}
        return numerator/denominator;
    }
 
    /**
     * 过滤
     * @param distances 计算后的皮尔森相关系数
     * @param otherRecommendUsers   其他用户的数据集
     * @param currentRecommendUser  被计算用户的数据
     * @return  返回过滤后的相似度高的结果
     */
    public List<RecommendItem> filtering(
            Map<Double, String> distances, List<RecommendUser> otherRecommendUsers, RecommendUser currentRecommendUser){
        List<String> nearList = new ArrayList<>(distances.values());
        List<Double> scores = new ArrayList<>(distances.keySet());
        List<List<Object>> nears= new ArrayList<>();
        List<RecommendItem> recommendationRecommendItems = new ArrayList<>();
        for (int i = nearList.size() - 1; i >= 0; i--){
            if (scores.get(i) > 0){
                ArrayList<Object> objects = new ArrayList<>();
                objects.add(nearList.get(i));
                objects.add(scores.get(i));
                nears.add(objects);
            }
        }
        for (List<Object> near : nears) {
            //找到邻居看过的商品
            RecommendUser neighborRatings = new RecommendUser();
            for (RecommendUser recommendUser : otherRecommendUsers) {
                if (near.get(0).equals(recommendUser.getId())) {
                    neighborRatings = recommendUser;
                }
            }
 
            //排除掉相同的商品
            boolean t;
            for (RecommendItem recommendNeighborGood : neighborRatings.getRecommendItemList()) {
                t = true;
                for (RecommendItem recommendUserGood : currentRecommendUser.getRecommendItemList()) {
                    if (recommendNeighborGood.getId().equals(recommendUserGood.getId())) {
                        t = false;
                        break;
                    }
                }
                if (t){
                    recommendationRecommendItems.add(recommendNeighborGood);
                }
            }
        }
        Collections.sort(recommendationRecommendItems);
        return recommendationRecommendItems;
    }
 
    /**
     * 推荐函数入口
     * @param otherRecommendUsers   其他用户数据集
     * @param currentRecommendUser  当前用户数据
     * @return
     */
    public List<RecommendItem> recommend(
            List<RecommendUser> otherRecommendUsers, RecommendUser currentRecommendUser) {
        //找到皮尔逊积矩相关系数（Pearson product-moment correlation coefficient）大于零的用户
        Map<Double, String> distances = computeNearestNeighbor(otherRecommendUsers, currentRecommendUser);
        return filtering(distances, otherRecommendUsers, currentRecommendUser);
    }
}